package security.security.decide;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.FilterInvocation;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.util.StringUtils;
import security.security.JwtUtil;
import security.security.login.CustomUserDetailsService;
import security.security.login.SecurityUser;

import java.util.*;

public class CustomAccessDecisionManager implements AccessDecisionManager {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    private PathMatcher pathMatcher = new AntPathMatcher();
    private SecuritySettings securitySettings;
    private CustomUserDetailsService userDetailsService;
    private Map<String, Collection<ConfigAttribute>> resourceMap = null;//读数据库

    public CustomAccessDecisionManager(SecuritySettings securitySettings, CustomUserDetailsService userDetailsService) {
        this.securitySettings = securitySettings;
        this.userDetailsService = userDetailsService;
        this.loadResourceMatchAuthority();
    }

    @Override
    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> urlHasRoles)
            throws AccessDeniedException, InsufficientAuthenticationException {

        String requestUrl = ((FilterInvocation) object).getRequestUrl();
        urlHasRoles = getAttributes(requestUrl);
        logger.debug("requestUrl: {}, urlHasRoles: {}", requestUrl, urlHasRoles);

        // 不需要鉴权的url
        if (urlHasRoles == null || urlHasRoles.isEmpty()) {
            return;
        }

        // 白名单的url
        String[] permitAllUrls = securitySettings.getPermitAll().split(",");
        for(String permitAllUrl : permitAllUrls){
            if(pathMatcher.match(permitAllUrl, requestUrl)){
                //permit all
                return;
            }
        }

        // 黑名单的url
        String[] denyUrls = securitySettings.getDenied().split(",");
        for(String denyUrl : denyUrls){
            if(pathMatcher.match(denyUrl, requestUrl)){
                logger.debug("禁止访问");
                throw new AccessDeniedException("禁止访问");
            }
        }

        // check token
        String[] userHasRoles = null;
        try{
            //user roles
            userHasRoles = JwtUtil.getCurrentUserRoles().split(",");
            logger.debug("userHasRoles: {}", userHasRoles);
        }catch (Exception e){
            e.printStackTrace();
            throw new AccessDeniedException("禁止访问");
        }

        //urlHasRoles VS userHasRoles
        for(ConfigAttribute needRole : urlHasRoles){
            for (String userRole : userHasRoles) {
                logger.debug("needRole: {}, userRole: {}", needRole.getAttribute(), userRole);
                if (userRole.equals(needRole.getAttribute())) {
                    return;
                }
            }
        }
        logger.debug("禁止访问");
        throw new AccessDeniedException("禁止访问");
    }

    private void loadResourceMatchAuthority() {

        resourceMap = new HashMap<>();

        if (StringUtils.isEmpty(securitySettings.getUrlRoles())){
            logger.error("'security-config.url-roles' must be set");
        }

        String[] resources = securitySettings.getUrlRoles().split(";");
        for(String resource : resources){
            String[] urls = resource.split("=");
            String[] roles = urls[1].split(",");
            Collection<ConfigAttribute> list = new ArrayList<>();
            for(String role : roles){
                ConfigAttribute config = new SecurityConfig(role.trim());
                list.add(config);
            }
            //key：url, value：roles
            resourceMap.put(urls[0].trim(), list);
        }
        logger.debug("Loaded UrlRoles Resources.");
    }

    public Collection<ConfigAttribute> getAttributes(String requestUrl) throws IllegalArgumentException {

        if(resourceMap == null){
            loadResourceMatchAuthority();
            logger.debug("resourceMap is {}", resourceMap);
        }

        Iterator<String> iterator = resourceMap.keySet().iterator();
        while (iterator.hasNext()) {
            String resourceUrl = iterator.next();
            if (pathMatcher.match(resourceUrl, requestUrl)) {
                return resourceMap.get(resourceUrl);
            }
        }
        return resourceMap.get(requestUrl);
    }

    @Override
    public boolean supports(ConfigAttribute attribute) {
        return true;
    }

    @Override
    public boolean supports(Class<?> clazz) {
        return true;
    }
}
